package com.googlecode.ddplan;

import java.lang.reflect.Method;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class DDPlanGenerator {

	private String domainName;
	private Map<String, Object> objects;
	@SuppressWarnings("rawtypes")
	private Set<Class> collectedClasses;

	@SuppressWarnings("rawtypes")
	public DDPlanGenerator() {
		objects = new HashMap<String, Object>();
		collectedClasses = new HashSet<Class>();
	}

	public void setDomainName(String name) {
		domainName = name.toUpperCase();
	}

	public void register(Collection<Object> values) {
		for (Object object : values) {
			Class<? extends Object> klass = object.getClass();
			if (!klass.isAnnotationPresent(PDDLType.class)) {
				throw new RuntimeException("Trying to register an object whose class is not annotated with "
						+ PDDLType.class.getSimpleName() + ". Class: " + klass.getName());
			}

			collectedClasses.add(klass);
			Object objName = null;
			try {
				for (java.lang.reflect.Field f : klass.getDeclaredFields()) {
					if (f.getAnnotation(PDDLName.class) != null) {
						f.setAccessible(true);
						objName = f.get(object);
						break;
					}
				}
			} catch (Exception e) {
				throw new RuntimeException("Unable to register object: " + object, e);
			}

			if (objName == null) {
				throw new RuntimeException("No annotated " + PDDLName.class.getSimpleName() + " field for class " + klass.getName());
			}

			String name = objName.toString();
			objects.put(name, object);
		}
	}

	protected String getTypes() {
		if (objects.size() == 0) {
			return "";
		}

		List<String> elements = new ArrayList<String>();
		elements.add(":TYPES");
		for (Object object : objects.values()) {
			elements.add(object.getClass().getSimpleName().toUpperCase());
		}
		return formatStringList(elements);
	}

	protected String getDomain() {
		return "(DOMAIN " + domainName + ")";
	}

	protected String getRequirements() {
		return "(:REQUIREMENTS :STRIPS :EQUALITY :TYPING)";
	}

	protected String getPredicates() {
		List<String> predicatesFormatted = new ArrayList<String>();

		for (Class<?> klass : collectedClasses) {
			for (Method m : klass.getDeclaredMethods()) {
				if (m.getAnnotation(PDDLState.class) != null) {
					m.setAccessible(true);
					List<String> predicateStrings = new ArrayList<String>();
					predicateStrings.add(m.getName());
					predicateStrings.add("?" + klass.getSimpleName().toUpperCase());
					predicateStrings.add("-");
					predicateStrings.add(klass.getSimpleName().toUpperCase());

					int parameterVariableIndex = 0;
					for (Class<?> parameterClass : m.getParameterTypes()) {
						predicateStrings.add("?" + parameterClass.getSimpleName().toLowerCase() + parameterVariableIndex);
						predicateStrings.add("-");
						predicateStrings.add(parameterClass.getSimpleName());
						parameterVariableIndex++;
					}

					predicatesFormatted.add(formatStringList(predicateStrings));
				}
			}
		}

		if (predicatesFormatted.size() > 0) {
			List<String> predicateResult = new ArrayList<String>();
			predicateResult.add(":PREDICATES");
			predicateResult.addAll(predicatesFormatted);
			return formatStringList(predicateResult);
		}
		return "";
	}

	private String formatStringList(List<String> list) {
		StringBuilder sb = new StringBuilder();
		for (String element : list) {
			sb.append(element + " ");
		}
		return "(" + sb.toString().trim() + ")";
	}

	protected ValidationResult validateActions() {
		ValidationResult otherValidation = new ValidationResult();
		ValidationResult actionsValidation = otherValidation;
		for (Method action : getActionMethods()) {
			List<String> states = new ArrayList<String>();
			states.addAll(Arrays.asList(action.getAnnotation(PDDLAction.class).precond()));
			states.addAll(Arrays.asList(action.getAnnotation(PDDLAction.class).effects()));
			for (String state : states) {
				StateAction stateAction = new StateAction(state.trim(), action);
				try {
					stateAction.parse();
					ValidationResult stateValidation = stateAction.validate();
					actionsValidation.aggregate(stateValidation);
				} catch (ParseException e) {
					actionsValidation.aggregate(new ValidationResult("Unablee to parse state " + state + " for action " + action.getName()
							+ " in class " + action.getClass().getName()));
				}
			}
		}
		return actionsValidation;
	}

	protected List<String> getActions() {
		List<String> actions = new ArrayList<String>();
		for (Method action : getActionMethods()) {
			actions.add(formatAction(action));
		}
		return actions;
	}

	private String formatAction(Method action) {
		List<String> actionStrings = new ArrayList<String>();
		actionStrings.add(":ACTION");
		actionStrings.add(action.getName());

		if (action.getParameterTypes().length > 0) {
			actionStrings.add(":PARAMETERS");

			List<String> parameterList = new ArrayList<String>();
			Map<String, String> mapVariableType = new HashMap<String, String>();
			for (int i = 0; i <= action.getParameterTypes().length; i++) {
				mapVariableType.put("?" + i, action.getParameterTypes()[i].getClass().getSimpleName());
			}
			
		}
		// int
		for (Class<?> parameterClass : action.getParameterTypes()) {

		}
		return formatStringList(actionStrings);
	}

	private List<Method> getActionMethods() {
		List<Method> actions = new ArrayList<Method>();
		for (Class<?> klass : collectedClasses) {
			for (Method m : klass.getDeclaredMethods()) {
				if (m.getAnnotation(PDDLAction.class) != null) {
					m.setAccessible(true);
					actions.add(m);
				}
			}
		}
		return actions;
	}

}
